package net.nohaven.proj.javeau.crypt.streams;

import java.io.IOException;
import java.io.InputStream;

import net.nohaven.proj.javeau.crypt.engine.Phelix;

public class PhelixInputStream extends InputStream {
	private static final int BUFFER_DIM = 4096;

	private InputStream in;

	private long available;

	private Phelix encoder = new Phelix(Phelix.PHELIX_MAC_SIZE);

	private byte[] buffer = new byte[BUFFER_DIM];
	private byte[] bufferOut = new byte[BUFFER_DIM];

	private int limit;

	private int idx;

	private boolean closeUnderlying;

	public PhelixInputStream(InputStream in, byte[] key, byte[] iv, long size)
			throws IOException {
		this(in, key, iv, size, true);
	}

	public PhelixInputStream(InputStream in, byte[] key, byte[] iv, long size,
			boolean closeUnderlying) throws IOException {
		this.in = in;

		this.available = size;

		this.closeUnderlying = closeUnderlying;

		byte[] nonce = new byte[16];
		System.arraycopy(iv, 0, nonce, 0, Math.min(iv.length, 16));

		encoder.setupKey(Phelix.MODE_DECRYPT, key);
		encoder.setupNonce(nonce);
	}

	private boolean fillBuffer() throws IOException {
		limit = in.read(buffer, 0, Math.min((int) available, buffer.length));
		if (limit < 1)
			return false;

		encoder.process(buffer, 0, bufferOut, 0, limit);

		idx = 0;
		return true;
	}

	public int read(byte[] bytes, int off, int len) throws IOException {
		for (int i = 0; i < len; i++) {
			int read = read();
			if (read < 0)
				return i;
			bytes[off + i] = (byte) read;
		}
		return len;
	}

	public int read(byte[] bytes) throws IOException {
		return read(bytes, 0, bytes.length);
	}

	public int read() throws IOException {
		if (idx == limit)
			fillBuffer();
		if (limit < 1)
			return -1;
		available--;
		return bufferOut[idx++] & 0xFF;
	}

	public void close() throws IOException {
		if (closeUnderlying)
			in.close();
	}

	public byte[] getMac() {
		byte[] mac = new byte[Phelix.PHELIX_MAC_SIZE >> 3];
		encoder.finalize(mac, 0);
		return mac;
	}

	public int available() throws IOException {
		return (int) available;
	}

	public synchronized void mark(int arg0) {
		throw new UnsupportedOperationException();
	}

	public boolean markSupported() {
		return false;
	}

	public synchronized void reset() throws IOException {
		throw new UnsupportedOperationException();
	}

	public long skip(long arg0) throws IOException {
		throw new UnsupportedOperationException();
	}
}
